In this example, we are using a CITEseq dataset of mouse glioblastoma model.This dataset contains CD45+ sorted cells from a GL261 glioblastoma tumor of wild type and CCR2 knockout mice (WT4 and KO4).You can download the expression matrix here.
suppressPackageStartupMessages({
library(Seurat)
library(scater)
library(dplyr)
library(VennDiagram)
library(mvoutlier)
library(harmony)
library(clustree)
library(cowplot)
library(scDblFinder)
library(ggplot2)
library(ggrepel)
})
Set the working directory
knitr::opts_knit$set(root.dir = "path/to/directory")
1) LOADING THE INPUT DATA
We use the Read10X function to read in the expresion matrix from the Cell Ranger output, which returns a list of two matrices: “Gene Expression” and “Antibody Capture”. We load the “Gene Expression” matrix into a Seurat object.
seur<-CreateSeuratObject(counts = Read10X(paste0("data/Citeseq_mouse_GBM/filtered_feature_bc_matrix"))[["Gene Expression"]] )
10X data contains more than one type and is being returned as a list containing matrices of each type.
### For single-cell RNA sequencing experiment wihout feature barcoding, use:
### seur<-CreateSeuratObject(counts = Read10X(paste0("path/to/filtered_feature_bc_matrix")) )
Identify the different samples based on the cell barcode names
head(colnames(seur))
[1] "AAACCCACAAGCGCTC-1" "AAACCCACAAGTCCCG-1" "AAACCCACACTGGCCA-1" "AAACCCACAGAGGACT-1" "AAACCCACAGCTGGTC-1" "AAACCCACAGTAGAAT-1"
seur$sample<-sapply(strsplit(colnames(seur), "-"), "[[",2)
table(seur$sample)
1 2
15850 12434
seur$sample<-plyr::mapvalues(seur$sample, from=1:2, to=c("KO1","WT1"))
Alternatively, if starting from the individual expression matrices for each sample,we can load them as a list of seurat objects and then merge them using the Seurat function “merge”:
# sample_folder_names=c("KO1","WT1")
### sample_folder_names - a vector with the names of the folders that contain the Cellranger outputs for each sample
# seur=list()
# for ( i in c(sample_folder_names)) {
# seur[[i]]<-CreateSeuratObject(counts = Read10X(paste0(i,"/filtered_feature_bc_matrix"))[["Gene Expression"]] )
# seur[[i]]$sample=i
# }
# seur<-merge(seur[[1]],seur[2:length(seur)])
2) QC: CELLS
2.1) Find outliers for total UMI counts, number of genes and percent mitohondrial genes per cell.
Outliers are determined based on the median absolute deviation (MAD). The goal is to remove the long tail before/after the peak in the sitribution of the QC metric. It is possible to modify the nmads parameter (minimum number of MADs away from median, required for a value to be called an outlier), or to set the threshold manually (e.g. remove all cells with percent mitochondrial genes above 40).
Calculate % mitochondrial genes per cell
seur[["perc.mito"]] <- PercentageFeatureSet(seur, pattern = "^mt-")
summary(seur$perc.mito)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 3.179 4.701 7.913 6.769 96.575
- UMI counts per cell
outliers=c()
for ( i in unique(seur$sample)){
outliers=c(outliers,
scater::isOutlier(seur$nCount_RNA[seur$sample==i], nmads=3, type="lower", log=TRUE)
)
}
seur$nUMI.outlier.low <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$nUMI.outlier.low))
Outliers: 1
Create histograms
for ( i in unique(seur$sample)){
hist(seur$nCount_RNA[seur$sample==i],
breaks = 100,xlab="nCount_RNA",
main=paste0("Total UMI counts per cell: ",i))
if(sum(seur$sample==i & seur$nUMI.outlier.low)!=0)
abline(v = max(seur$nCount_RNA[seur$sample==i & seur$nUMI.outlier.low]), col = "red")
}


Create violin plots
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, nCount_RNA)) +
geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=nUMI.outlier.low)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}


- Number of genes per cell
outliers=c()
for ( i in unique(seur$sample)){
outliers=c(outliers,
scater::isOutlier(seur$nFeature_RNA[seur$sample==i], nmads=3, type="lower", log=TRUE)
)
}
seur$nGene.outlier.low <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$nGene.outlier.low))
Outliers: 1009
Create histograms
for ( i in unique(seur$sample)){
hist(seur$nFeature_RNA[seur$sample==i],
breaks = 100,xlab="nCount_RNA",
main=paste0("Number of genes per cell: ",i))
if(sum(seur$sample==i & seur$nGene.outlier.low)!=0)
abline(v = max(seur$nFeature_RNA[seur$sample==i & seur$nGene.outlier.low]), col = "red")
}


Create violin plots
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, nFeature_RNA)) +
geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=nGene.outlier.low)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}


- Proportion of mitohondrial genes per cell
outliers=c()
for ( i in unique(seur$sample)){
outliers=c(outliers,
scater::isOutlier(seur$perc.mito[seur$sample==i], nmads=3, type="higher", log=TRUE)
)
}
seur$mito.outlier.high <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$mito.outlier.high))
Outliers: 1440
Create histograms
for ( i in unique(seur$sample)){
hist(seur$perc.mito[seur$sample==i],
breaks = 100,xlab="perc.mito",
main=paste0("% mito genes per cell: ",i))
if(sum(seur$sample==i & seur$mito.outlier.high)!=0)
abline(v = min(seur$perc.mito[seur$sample==i & seur$mito.outlier.high]), col = "red")
}


Create violin plots
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, perc.mito)) +
geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=mito.outlier.high)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
}


Overlap of cells, outliers for UMI counts, number of genes and % mitochodrial genes per cell
A large nuber of cells, outliers specifically for one QC metric might be a concern and require fuurhter investigation
for ( i in unique(seur$sample)){
v <-venn.diagram(
list (nUMI=colnames(seur)[seur$nUMI.outlier.low & seur$sample==i],
nGene=colnames(seur)[seur$nGene.outlier.low & seur$sample==i],
perc.mito=colnames(seur)[seur$mito.outlier.high & seur$sample==i]),
filename=NULL,main=i,
alpha = c( 0.5,0.5,0.5),
fill = c("green","orange","blue")
)
grid.newpage()
grid.draw(v)
rm(v)
}


How many cells will be removed during the filtering
cells.to.keep= ! (seur$mito.outlier.high | seur$nGene.outlier.low | seur$nUMI.outlier.low)
print(paste(ncol(seur)-
ncol(seur[,cells.to.keep]),
"cells to be removed"))
[1] "1524 cells to be removed"
Violin plots after filtering
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]][cells.to.keep& seur$sample==i, ]), aes(1, nCount_RNA)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=nUMI.outlier.low)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}


for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]][ cells.to.keep, ]), aes(1, nFeature_RNA)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=nGene.outlier.low)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}


for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(seur[[]][cells.to.keep, ]), aes(1, perc.mito)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=mito.outlier.high)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
}


Filter the outlier cells
seur_clean<-seur[, cells.to.keep]
print(dim(seur))
[1] 28692 28284
print(dim(seur_clean))
[1] 28692 26760
2.2) Get PCA outliers (Optional)
Get additional outliers, using multivariate outlier detection function of scater, based on PCA computed on QC metric data (uses the mvoutlier package)
sce=as.SingleCellExperiment(seur_clean)
##### Choose QC variables for authomatic outlier detection
selected_variables <- c("nCount_RNA",
"nFeature_RNA", "perc.mito")
setdiff(selected_variables, colnames(colData(sce)))
character(0)
Detect outliers
outliers=c()
for ( i in unique(seur$sample)){
sce_sample=runColDataPCA(sce[,sce$sample==i], outliers=T, variables=selected_variables)
outliers=c(outliers,sce_sample$outlier)
}
sce$outliers=outliers
seur_clean <- AddMetaData(object = seur_clean, metadata = colData(sce)[colnames(seur_clean),"outliers"], col.name = "pca.outlier")
cat("Outliers:",sum(seur_clean$pca.outlier))
Outliers: 430
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, nCount_RNA)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}


for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, nFeature_RNA)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}


for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, perc.mito)) +
geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
}


As the number of additional PCA outliers is large, we keep those cells for now and will consider later if they need to be removed or not
### Remove the PCA outlier cells
# seur_clean<-seur_clean[, cells.to.keep]
# print(dim(seur_clean))
3) QC: GENES
To define a cutoff of lowly-abundant genes, we plot the distribution of log-means across all genes. The cutoff is placed in middle of the rectangular component of the graph before the peak.
thresholds<-c(0.005,0.005)
ave.counts=list()
for ( i in 1: length(unique(seur_clean$sample))) {
ave.counts[[i]] <- rowMeans(as.matrix(GetAssayData(seur_clean, slot="counts")[,seur_clean$sample==unique(seur$sample)[i]]))
hist(log10(ave.counts[[i]]), breaks=100, main=paste0("Histogram of mean UMI counts per gene: ",unique(seur$sample)[i]), col="grey80",
xlab=expression(Log[10]~"mean count per gene"))
abline(v=log10(thresholds[i]), col="blue", lwd=2, lty=2)
}


Number of genes to keep
[1] "KO1"
FALSE TRUE
16241 12451
[1] "WT1"
FALSE TRUE
16471 12221
Filter out the lowly-abundant genes that overlap between all samples
i=1
genes.filter=names(usegenes[[i]][! usegenes[[i]]])
for ( i in 2: length(unique(seur_clean$sample))) {
genes.filter=intersect(genes.filter, names(usegenes[[i]][! usegenes[[i]]]))
}
usegenes.final=!rownames(seur_clean) %in% genes.filter
table(usegenes.final)
usegenes.final
FALSE TRUE
16021 12671
seur_clean<-seur_clean[usegenes.final, ]
4) CALCULATE DOUBLET SCORE
We estimate doublet score per cell, using the scDblFinder package,which simulates artificial doublets from cell clusters. It is preferable to look for doublets separately for each sample
doublet.score<- scDblFinder::scDblFinder(as.SingleCellExperiment(seur_clean), samples="sample", BPPARAM=
BiocParallel::MulticoreParam(3),returnType="table")
seur_clean <- AddMetaData(object = seur_clean, metadata = doublet.score[colnames(seur_clean),"score"], col.name = "doublet.score")
seur_clean <- AddMetaData(object = seur_clean, metadata = doublet.score[colnames(seur_clean),"class"], col.name = "doublet.class")
table(seur_clean$doublet.class)
doublet singlet
3881 22879
summary(seur_clean$doublet.score)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0000695 0.0078344 0.0356150 0.1956456 0.1480074 0.9999810
5) NORMALIZATION, HVG DETECTION and PCA
##### Normalize data
seur_clean <- NormalizeData(seur_clean,verbose = F)
##### HVG detection
seur_clean <- FindVariableFeatures(seur_clean,verbose=F)
##### Scale data per gene
seur_clean <- ScaleData(seur_clean,verbose=F)
##### PCA
seur_clean <- RunPCA(seur_clean, features =VariableFeatures(seur_clean) ,verbose=F)
Select PCs for downstream analysis of the dataset
Seurat provides a heuristic method to help us select PC components. It generates a ranking of PCs based on the percentage of variance explained by each of them
ElbowPlot(object = seur_clean,ndims =50)

Heatmap of the genes that drive each PC
DimHeatmap(seur_clean, dims = 1:30, cells = 5000, balanced = TRUE)

6) UMAP DIMENSIONALITY REDUCTION
Run Uniform Manifold Approximation and Projection (UMAP) dimensional reduction technique for visualisation of the data
### PC selection for downstream analysis
dims.use<-30
seur_clean <- RunUMAP(seur_clean, dims = 1:dims.use, verbose=F)
Visualise the UMAP plot, coloured by sample to check for batch effects
DimPlot(object = seur_clean, group.by = "sample",label=T, repel=T)

FeaturePlot(object = seur_clean, features = c("Ly6c2", "Ccr2"), split.by = "sample" )

The UMAP plot shows relatively good mixing by sample for most cells. Logically, monocytes are mainly present in Ccr2 WT cells, as visualized by Ccr2 and Ly6c2 expression.
7) BATCH CORRECTION
In case batch correction is necessary, it can be performed using the harmony package. Select a value for theta - diversity clustering penalty parameter (Default theta=2). Larger values of theta result in stronger integration, but can lead to over-correction)
theta.use<-1 # Here the theta parameter can be modified
seur_clean<-RunHarmony(seur_clean, group.by.vars="sample",theta =theta.use,
plot_convergence = TRUE, reduction.save =paste0("harmonyTheta",theta.use),
reduction.key = paste0("harmonyTheta",theta.use,"_"), verbose=F)

seur_clean <- RunUMAP(seur_clean,reduction =paste0("harmonyTheta",theta.use),
dims = 1:dims.use,verbose =F, reduction.name = paste0("umapHarmonyTheta",theta.use, "PC",dims.use),
reduction.key = paste0("umapHarmonyTheta",theta.use, "PC",dims.use,"_"))
### Visualise the harmony-corrected UMAP plot, coloured by sample to inspect if the batch effects were resolved
DimPlot(object = seur_clean,reduction = paste0("umapHarmonyTheta",theta.use, "PC",dims.use),
group.by = "sample",label=T, repel=T)+ggtitle(paste0("Samples: UMAP Harmony theta= ",theta.use, " PC= ",dims.use))

As the the samples already show good mixing before batch correction, and the library preparation was done on the same day for both samples, we are going to continue without batch correction
Visualize QC metrics
FeaturePlot(object = seur_clean,
features = c("nCount_RNA", "nFeature_RNA", "perc.mito","doublet.score"))

Visualize PCA outlier cells
DimPlot(object = seur_clean, group.by = "pca.outlier")

Visualize QC metrics for pca outliers, and the remaining cells
FeaturePlot(seur_clean, split.by ="pca.outlier" ,features=c("nCount_RNA", "nFeature_RNA", "perc.mito", "Mki67","Stmn1"),
keep.scale="feature", pt.size = 0.2)

The PCA outliers include cells with high percent mitochondrial genes, as well as cells with high UMI content and number of genes. Many of the latter express proliferation markers (Mki67, Stmn1), the active proliferation explaining their higher number of UMIs and genes. Therefore the PCA outliers will not be removed.
8) CLUSTERING
We run Louvain clustering with varying the clustering resolution between 0 and 2
seur_clean <- FindNeighbors(seur_clean, dims = 1:dims.use,
graph.name = paste0("RNA_snn_PC",dims.use), verbose=F)
for ( i in seq(0,2, 0.25))
seur_clean <- FindClusters(seur_clean, resolution = i, graph.name=paste0("RNA_snn_PC",dims.use), verbose=F)
If the batch correction is necessary for a specific dataset, clustering can be performed on the harmony-corrected PCA embeddings:
#seur_clean <- FindNeighbors(seur_clean, dims = 1:dims.use, reduction =paste0("harmonyTheta",theta.use), graph.name = paste0("RNA_snn_harmony_theta",theta.use, ".PC",dims.use), verbose=F)
#for ( i in seq(0,2, 0.25))
# seur_clean <- FindClusters(seur_clean, resolution = i, graph.name=paste0("RNA_snn_harmony_theta",theta.use, ".PC",dims.use), verbose=F)
Choosing a clustering resolution value
Plot of a clustering tree showing the relationship between clustering at different resolution (using the clustree package).This plot allows us to see how are the clusters related to each other and which ones are stable across different resolutions
clustree(seur_clean, prefix = paste0("RNA_snn_PC",dims.use,"_res."))+
ggtitle(paste("PC =",dims.use))
The `add` argument of `group_by()` is deprecated as of dplyr 1.0.0.
Please use the `.add` argument instead.
[90mThis warning is displayed once every 8 hours.[39m
[90mCall `lifecycle::last_warnings()` to see where this warning was generated.[39m

The clusters are relatively stable until resolution of 1
Visualize the clusters for several resolution values on the UMAP plot
plot<-list()
for ( res in c(0.25,0.5, 0.75,1))
plot[[as.character(res)]]<-DimPlot(seur_clean,label=T,repel=T, group.by = paste0("RNA_snn_PC",dims.use,"_res.",res))+
ggtitle(paste("PC =",dims.use,"res=",res))
plot_grid(plotlist=plot)

9) ANNOTATING THE CLUSTERS
Let’s find differentially expressed (DE) genes per cluster for resolution=1.
res=1
Idents(seur_clean)= paste0("RNA_snn_PC",dims.use,"_res.",res)
Idents(seur_clean)= factor(Idents(seur_clean),levels = 0:(length(unique(Idents(seur_clean)))-1))
DEgenes<-FindAllMarkers(seur_clean,min.cells.group=2,pseudocount.use = 0.01, max.cells.per.ident = 1000,verbose = F)
Next,we visualise the expression of the top marker genes for each cluster in a heatmap
features.plot=DEgenes%>%filter(avg_logFC>0)%>% group_by(cluster)%>%top_n(n=-3,wt=p_val_adj)%>%pull(gene) ### top 3 markers
length(features.plot)
[1] 83
seur_clean=ScaleData(seur_clean, features = rownames(seur_clean)) ### Scale the data for all genes to be able to visualise their expression with DoHeatmap
DoHeatmap(seur_clean, features = features.plot, assay = "RNA", angle = 90, label =T, size=4) +
scale_fill_gradient2(low = "blue", mid = "white",high = "red")+
theme(legend.position = "bottom")%>% suppressMessages()
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.

Check the distribution of each cluster per sample
data<-as.data.frame(table(seur_clean$sample, Idents(seur_clean)))
colnames(data)=c("sample","cluster","Freq")
ggplot(data, aes(x=cluster, y=Freq, fill=sample))+geom_bar(stat = "identity")+ggtitle(paste0("Number of cells per cluster (resolution =",res,")"))+theme_classic()

Visualise the expression of marker genes on a UMAP plot
FeaturePlot(seur_clean,features=c("Ccr2","Ly6c2","C1qa","Sparc","P2ry12","Tgfbi","Ms4a7","Mki67","Gata2","Hdc","Cpa3","S100a9","Csf3r","Flt3","Xcr1","Cd209a","H2-DMb2","Ccr7","Ccr9","Ms4a1","Jchain", "Cd3e","Cd8a","Cd4","Foxp3","Klrb1c","Sox2","Plp1"))

seur_clean$nCount_RNA_log=log2(seur_clean$nCount_RNA)
seur_clean$nFeature_RNA_log=log2(seur_clean$nFeature_RNA)
Visualise the expression of marker genes using a DotPlot
DotPlot(seur_clean,features=c("Ptprc","Ccr2","Ly6c2","Plac8","C1qa","Sparc","Sall1","Tgfbi","Ms4a7","Mki67","Stmn1","Mcm3","Ifit3","Ly6g","Csf3r","S100a9","S100a8","Flt3","Xcr1","Cd209a","H2-DMb2","Napsa","Siglech","Ccr9","Ccr7","Cacnb3","Ccl22", "Cd3e","Cd8a","Cd4","Lef1","Tcf7","Foxp3","Il2ra","Tcrg-C1","Trdc","Klrb1c","Ncr1","Gnly", "Cd19","Ms4a1","Sdc1","Jchain","Ms4a2", "Gata2","Cd200r3","Cdh1","Hgf","Hdc","Mcpt1","Cpa3","Sox2","Gjc3","Plp1", "doublet.score","perc.mito","nCount_RNA_log","nFeature_RNA_log"))+RotatedAxis()

Assign cell types to the clusters, based on the marker gene expression
Idents(seur_clean) <- plyr::mapvalues(x = Idents(seur_clean), from = 0:25,
to =c("TAM 1","CD8 T cells","TAM 2","TAM 3","TAM 4","Artefact","NK cells","cDC2","Basophils","Treg cells","TAM Proliferating","migDC","Monocytes","cDC1","T cells Proliferating","CD4 T cells 1","pDC","CD4 T cells 2","TAM 5","Plasma cells","B cells","TAM/T doublets","Mast cells","Neutrophils","Oligodendrocytes","TAM/Basophil doublets"))
DimPlot(seur_clean,repel =T,label=T)

DotPlot(seur_clean,features=c("Ptprc","Ccr2","Ly6c2","Plac8","C1qa","Sparc","Sall1","Tgfbi","Ms4a7","Mki67","Stmn1","Mcm3","Ifit3","Ly6g","Csf3r","S100a9","S100a8","Flt3","Xcr1","Cd209a","H2-DMb2","Napsa","Siglech","Ccr9","Ccr7","Cacnb3","Ccl22", "Cd3e","Cd8a","Cd4","Lef1","Tcf7","Foxp3","Il2ra","Tcrg-C1","Trdc","Klrb1c","Ncr1","Gnly", "Cd19","Ms4a1","Sdc1","Jchain","Ms4a2", "Gata2","Cd200r3","Cdh1","Hgf","Hdc","Mcpt1","Cpa3","Sox2","Gjc3","Plp1", "doublet.score","perc.mito","nCount_RNA_log","nFeature_RNA_log"))+RotatedAxis()

10) REMOVE DOUBLETS AND ARTEFACTS
Remove the doublets and artefacts - cluster with high mitochodrial content and the oligodendrocytes as the latter are probably dues to impurities during the CD45+ sorting
data<-as.data.frame(table(seur_clean$doublet.class, Idents(seur_clean)))
colnames(data)=c("doublet.class","cluster","Freq")
ggplot(data, aes(x=cluster, y=Freq, fill=doublet.class))+geom_bar(stat = "identity")+ggtitle(paste0("Number of cells per cluster (resolution =",res,")"))+theme_classic()+RotatedAxis()

cells.keep=WhichCells(seur_clean, idents =c("Artefact","TAM/T doublets","TAM/Basophil doublets","Oligodendrocytes"), invert = T )
cat(ncol(seur_clean)-length(cells.keep),"cells to be removed")
2350 cells to be removed
Remove cells and rerun HVG detection,PCA and UMAP
seur_artefacts_removed=seur_clean[,cells.keep]
seur_artefacts_removed <- FindVariableFeatures(seur_artefacts_removed,verbose=F)
seur_artefacts_removed <- ScaleData(seur_artefacts_removed,verbose=F)
seur_artefacts_removed <- RunPCA(seur_artefacts_removed, features =VariableFeatures(seur_artefacts_removed) ,verbose=F)
#### Select PCs for downstream analysis of the dataset
ElbowPlot(object = seur_artefacts_removed,ndims =50)

Heatmap of the genes that drive each PC
DimHeatmap(seur_artefacts_removed, dims = 1:30, cells = 5000, balanced = TRUE)

Run UMAP
dims.use<-30 ### Final PC selection for downstream analysis
seur_artefacts_removed <- RunUMAP(seur_artefacts_removed, dims = 1:dims.use, verbose=F)
DimPlot(object = seur_artefacts_removed, group.by = "sample",label=T, repel=T)+
ggtitle(paste0("Samples: UMAP PC= ",dims.use))

Explore DE genes between cluster TAM5 and TAM1 through Volcano plot
contrast.name="TAM5vsTAM1"
DEG_TAM5vsTAM1=FindMarkers(seur_artefacts_removed,ident.1 = "TAM 5",ident.2 = "TAM 1",
pseudocount.use = 0.01, verbose = F)
DEG_TAM5vsTAM1$gene=rownames(DEG_TAM5vsTAM1)
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
geom_point(alpha = 1, size=2,
aes(col = p_val_adj<1e-20 & (avg_logFC>1 | avg_logFC< -1))) +
scale_color_manual(values = c("dark grey", "brown1")) +
geom_text_repel(data=subset(DEG_TAM5vsTAM1,
p_val_adj<1e-100 & (avg_logFC>1 | avg_logFC< -1)),aes(label = gene), size =4)+
theme_classic()+theme(legend.position = "none")+ggtitle(contrast.name)

Top GO enriched pathway:
index=1
gset.symbols=GO.metascape[index,"Symbols"]
gset.symbols=strsplit(gset.symbols, ",")[[1]]
GOterm=GO.metascape[index,"Description"]
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
geom_point(alpha = 1, size=2,
aes(color=gene %in% gset.symbols)) +
scale_color_manual(values = c("dark grey", "brown1")) +
geom_text_repel(data=subset(DEG_TAM5vsTAM1,
gene %in% gset.symbols),aes(label = gene), size =4)+
theme_classic()+theme(legend.position = "none")+ggtitle(paste0( contrast.name, ": ", GOterm, " pathway"))

“response to hypoxia” pathway:
GOterm="response to hypoxia"
gset.symbols=GO.metascape[GO.metascape$Description==GOterm,"Symbols"]
gset.symbols=strsplit(gset.symbols, ",")[[1]]
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
geom_point(alpha = 1, size=2,
aes(color=gene %in% gset.symbols)) +
scale_color_manual(values = c("dark grey", "brown1")) +
geom_text_repel(data=subset(DEG_TAM5vsTAM1,
gene %in% gset.symbols),aes(label = gene), size =4)+
theme_classic()+theme(legend.position = "none")+ggtitle(paste0( contrast.name, ": ", GOterm, " pathway"))

DimPlot(object = seur_artefacts_removed,label=T, repel=T)+
ggtitle(paste0("Cell annotation: UMAP PC= ",dims.use))

Save object
saveRDS(seur_clean, file="Citeseq_mouse_GBM.seuratObj.rds")
sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.4 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 LC_PAPER=en_US.UTF-8 LC_NAME=en_US.UTF-8
[9] LC_ADDRESS=en_US.UTF-8 LC_TELEPHONE=en_US.UTF-8 LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=en_US.UTF-8
attached base packages:
[1] grid parallel stats4 stats graphics grDevices utils datasets methods base
other attached packages:
[1] ggrepel_0.9.1 scDblFinder_1.4.0 cowplot_1.1.1 clustree_0.4.3
[5] ggraph_2.0.4 harmony_1.0 Rcpp_1.0.6 mvoutlier_2.0.9
[9] sgeostat_1.0-27 VennDiagram_1.6.20 futile.logger_1.4.3 dplyr_1.0.3
[13] scater_1.18.3 ggplot2_3.3.4 SingleCellExperiment_1.12.0 SummarizedExperiment_1.20.0
[17] Biobase_2.50.0 GenomicRanges_1.42.0 GenomeInfoDb_1.26.7 IRanges_2.24.1
[21] S4Vectors_0.28.1 BiocGenerics_0.36.1 MatrixGenerics_1.2.1 matrixStats_0.60.0
[25] Seurat_3.2.3
loaded via a namespace (and not attached):
[1] scattermore_0.7 prabclus_2.3-2 GGally_2.1.2 tidyr_1.1.2 knitr_1.30
[6] irlba_2.3.3 DelayedArray_0.16.3 data.table_1.13.6 rpart_4.1-15 RCurl_1.98-1.4
[11] generics_0.1.0 lambda.r_1.2.4 RANN_2.6.1 proxy_0.4-24 future_1.21.0
[16] spatstat.data_2.1-0 httpuv_1.5.5 assertthat_0.2.1 viridis_0.5.1 xfun_0.20
[21] rJava_0.9-13 hms_1.0.0 evaluate_0.14 promises_1.1.1 fansi_0.4.2
[26] DEoptimR_1.0-8 readxl_1.3.1 igraph_1.2.6 DBI_1.1.1 htmlwidgets_1.5.3
[31] reshape_0.8.8 purrr_0.3.4 ellipsis_0.3.1 RSpectra_0.16-0 ks_1.13.1
[36] backports_1.2.1 sROC_0.1-2 deldir_1.0-6 sparseMatrixStats_1.2.0 vctrs_0.3.6
[41] ROCR_1.0-11 abind_1.4-5 withr_2.4.0 ggforce_0.3.2 robustbase_0.93-7
[46] checkmate_2.0.0 vcd_1.4-8 sctransform_0.3.2 scran_1.18.3 mclust_5.4.7
[51] goftest_1.2-2 cluster_2.1.0 lazyeval_0.2.2 laeken_0.5.1 crayon_1.4.0
[56] labeling_0.4.2 edgeR_3.32.1 pkgconfig_2.0.3 zCompositions_1.3.4 tweenr_1.0.1
[61] nlme_3.1-149 vipor_0.4.5 nnet_7.3-14 rlang_0.4.10 globals_0.14.0
[66] diptest_0.75-7 lifecycle_0.2.0 pls_2.7-3 miniUI_0.1.1.1 rsvd_1.0.3
[71] cellranger_1.1.0 polyclip_1.10-0 lmtest_0.9-38 Matrix_1.3-2 carData_3.0-4
[76] boot_1.3-25 zoo_1.8-8 beeswarm_0.2.3 ggridges_0.5.3 png_0.1-7
[81] viridisLite_0.3.0 bitops_1.0-7 KernSmooth_2.23-17 DelayedMatrixStats_1.12.2 stringr_1.4.0
[86] parallelly_1.23.0 beachmat_2.6.4 scales_1.1.1 magrittr_2.0.1 plyr_1.8.6
[91] ica_1.0-2 zlibbioc_1.36.0 compiler_4.0.3 hdrcde_3.4 dqrng_0.2.1
[96] RColorBrewer_1.1-2 rrcov_1.5-5 fitdistrplus_1.1-3 cli_2.2.0 XVector_0.30.0
[101] listenv_0.8.0 patchwork_1.1.1 pbapply_1.4-3 formatR_1.7 MASS_7.3-53
[106] mgcv_1.8-33 tidyselect_1.1.0 stringi_1.5.3 forcats_0.5.0 yaml_2.2.1
[111] BiocSingular_1.6.0 locfit_1.5-9.4 tools_4.0.3 future.apply_1.7.0 rio_0.5.16
[116] rstudioapi_0.13 bluster_1.0.0 foreign_0.8-79 gridExtra_2.3 farver_2.0.3
[121] Rtsne_0.15 digest_0.6.27 shiny_1.5.0 pracma_2.3.3 fpc_2.2-9
[126] car_3.0-10 xlsx_0.6.5 scuttle_1.0.4 later_1.1.0.1 RcppAnnoy_0.0.18
[131] fda_5.1.9 httr_1.4.2 kernlab_0.9-29 colorspace_2.0-0 tensor_1.5
[136] rainbow_3.6 ranger_0.12.1 reticulate_1.18 truncnorm_1.0-8 splines_4.0.3
[141] uwot_0.1.10 statmod_1.4.35 spatstat.utils_2.2-0 graphlayouts_0.7.1 xlsxjars_0.6.1
[146] sp_1.4-5 xgboost_1.3.2.1 flexmix_2.3-17 plotly_4.9.3 xtable_1.8-4
[151] jsonlite_1.7.2 fds_1.8 futile.options_1.0.1 spatstat_1.64-1 tidygraph_1.2.0
[156] modeltools_0.2-23 R6_2.5.0 NADA_1.6-1.1 pillar_1.4.7 htmltools_0.5.2
[161] mime_0.9 glue_1.4.2 fastmap_1.1.0 VIM_6.1.0 BiocParallel_1.24.1
[166] BiocNeighbors_1.8.2 cvTools_0.3.2 class_7.3-17 codetools_0.2-16 pcaPP_1.9-74
[171] mvtnorm_1.1-1 lattice_0.20-41 tibble_3.0.5 curl_4.3 ggbeeswarm_0.6.0
[176] leiden_0.3.6 zip_2.1.1 openxlsx_4.2.3 survival_3.2-7 limma_3.46.0
[181] rmarkdown_2.6 munsell_0.5.0 e1071_1.7-6 GenomeInfoDbData_1.2.4 haven_2.3.1
[186] reshape2_1.4.4 gtable_0.3.0 robCompositions_2.3.0
LS0tCnRpdGxlOiAiU2luZ2xlLWNlbGwgUk5BIHNlcXVlbmNpbmcgZGF0YSBwcm9jZXNzaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZGF0ZTogJ0NyZWF0ZWQgb246IGByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJUIgJWQsICVZIilgJwotLS0KCkluIHRoaXMgZXhhbXBsZSwgd2UgYXJlIHVzaW5nIGEgQ0lURXNlcSBkYXRhc2V0IG9mIG1vdXNlIGdsaW9ibGFzdG9tYSBtb2RlbC5UaGlzIGRhdGFzZXQgY29udGFpbnMgQ0Q0NSsgc29ydGVkIGNlbGxzIGZyb20gYSBHTDI2MSBnbGlvYmxhc3RvbWEgdHVtb3Igb2Ygd2lsZCB0eXBlIGFuZCBDQ1IyIGtub2Nrb3V0IG1pY2UgKFdUNCBhbmQgS080KS5Zb3UgY2FuIGRvd25sb2FkIHRoZSBleHByZXNzaW9uIG1hdHJpeCBbaGVyZV0oaHR0cHM6Ly93d3cuYnJhaW5pbW11bmVhdGxhcy5vcmcvZGF0YV9maWxlcy90b0Rvd25sb2FkL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4X01vdXNlR0JNY2l0ZVNlcS56aXApLgoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShzY2F0ZXIpCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KFZlbm5EaWFncmFtKQogIGxpYnJhcnkobXZvdXRsaWVyKQogIGxpYnJhcnkoaGFybW9ueSkKICBsaWJyYXJ5KGNsdXN0cmVlKQogIGxpYnJhcnkoY293cGxvdCkKICBsaWJyYXJ5KHNjRGJsRmluZGVyKQogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KGdncmVwZWwpCn0pCmBgYAoKU2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeQpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAicGF0aC90by9kaXJlY3RvcnkiKQpgYGAKCiMjIyAxKSBMT0FESU5HIFRIRSBJTlBVVCBEQVRBCldlIHVzZSB0aGUgUmVhZDEwWCBmdW5jdGlvbiB0byByZWFkIGluIHRoZSBleHByZXNpb24gbWF0cml4IGZyb20gdGhlIENlbGwgUmFuZ2VyIG91dHB1dCwgd2hpY2ggcmV0dXJucyBhIGxpc3Qgb2YgdHdvIG1hdHJpY2VzOiAiR2VuZSBFeHByZXNzaW9uIiBhbmQgIkFudGlib2R5IENhcHR1cmUiLiBXZSBsb2FkIHRoZSAiR2VuZSBFeHByZXNzaW9uIiBtYXRyaXggaW50byBhIFNldXJhdCBvYmplY3QuCmBgYHtyfQpzZXVyPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gUmVhZDEwWChwYXN0ZTAoImRhdGEvQ2l0ZXNlcV9tb3VzZV9HQk0vZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgiKSlbWyJHZW5lIEV4cHJlc3Npb24iXV0gKQojIyMgRm9yIHNpbmdsZS1jZWxsIFJOQSBzZXF1ZW5jaW5nIGV4cGVyaW1lbnQgd2lob3V0IGZlYXR1cmUgYmFyY29kaW5nIGxpYnJhcnksIHVzZToKIyMjIHNldXI8LUNyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBSZWFkMTBYKHBhc3RlMCgicGF0aC90by9maWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpKSApCmBgYAoKSWRlbnRpZnkgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGJhc2VkIG9uIHRoZSBjZWxsIGJhcmNvZGUgbmFtZXMKYGBge3J9CmhlYWQoY29sbmFtZXMoc2V1cikpCmBgYApgYGB7cn0Kc2V1ciRzYW1wbGU8LXNhcHBseShzdHJzcGxpdChjb2xuYW1lcyhzZXVyKSwgIi0iKSwgIltbIiwyKQp0YWJsZShzZXVyJHNhbXBsZSkKYGBgCmBgYHtyfQpzZXVyJHNhbXBsZTwtcGx5cjo6bWFwdmFsdWVzKHNldXIkc2FtcGxlLCBmcm9tPTE6MiwgdG89YygiS08xIiwiV1QxIikpCmBgYApBbHRlcm5hdGl2ZWx5LCBpZiBzdGFydGluZyBmcm9tIHRoZSBpbmRpdmlkdWFsIGV4cHJlc3Npb24gbWF0cmljZXMgZm9yIGVhY2ggc2FtcGxlLHdlIGNhbiBsb2FkIHRoZW0gYXMgYSBsaXN0IG9mIHNldXJhdCBvYmplY3RzIGFuZCB0aGVuIG1lcmdlIHRoZW0gdXNpbmcgdGhlIFNldXJhdCBmdW5jdGlvbiAibWVyZ2UiOgpgYGB7cn0KIyBzYW1wbGVfZm9sZGVyX25hbWVzPWMoIktPMSIsIldUMSIpCiMjIyBzYW1wbGVfZm9sZGVyX25hbWVzIC0gYSB2ZWN0b3Igd2l0aCB0aGUgbmFtZXMgb2YgdGhlIGZvbGRlcnMgdGhhdCBjb250YWluIHRoZSBDZWxscmFuZ2VyIG91dHB1dHMgZm9yIGVhY2ggc2FtcGxlCiMgc2V1cj1saXN0KCkKIyBmb3IgKCBpIGluIGMoc2FtcGxlX2ZvbGRlcl9uYW1lcykpIHsgIAojICAgc2V1cltbaV1dPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gUmVhZDEwWChwYXN0ZTAoaSwiL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4IikpW1siR2VuZSBFeHByZXNzaW9uIl1dICkKIyAgIHNldXJbW2ldXSRzYW1wbGU9aQojIH0KIyBzZXVyPC1tZXJnZShzZXVyW1sxXV0sc2V1clsyOmxlbmd0aChzZXVyKV0pCmBgYAoKCgojIyMgMikgUUM6IENFTExTCiMjIyMgMi4xKSAgRmluZCBvdXRsaWVycyBmb3IgdG90YWwgVU1JIGNvdW50cywgbnVtYmVyIG9mIGdlbmVzIGFuZCBwZXJjZW50IG1pdG9ob25kcmlhbCBnZW5lcyBwZXIgY2VsbC4gCk91dGxpZXJzIGFyZSBkZXRlcm1pbmVkIGJhc2VkIG9uIHRoZSBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9uIChNQUQpLiBUaGUgZ29hbCBpcyB0byByZW1vdmUgdGhlIGxvbmcgdGFpbCBiZWZvcmUvYWZ0ZXIgdGhlIHBlYWsgaW4gdGhlIHNpdHJpYnV0aW9uIG9mIHRoZSBRQyBtZXRyaWMuIEl0IGlzIHBvc3NpYmxlIHRvIG1vZGlmeSB0aGUgbm1hZHMgcGFyYW1ldGVyIChtaW5pbXVtIG51bWJlciBvZiBNQURzIGF3YXkgZnJvbSBtZWRpYW4sIHJlcXVpcmVkIGZvciBhIHZhbHVlIHRvIGJlIGNhbGxlZCBhbiBvdXRsaWVyKSwgb3IgdG8gc2V0IHRoZSB0aHJlc2hvbGQgbWFudWFsbHkgKGUuZy4gcmVtb3ZlIGFsbCBjZWxscyB3aXRoIHBlcmNlbnQgbWl0b2Nob25kcmlhbCBnZW5lcyBhYm92ZSA0MCkuIAoKQ2FsY3VsYXRlICUgbWl0b2Nob25kcmlhbCBnZW5lcyBwZXIgY2VsbApgYGB7cn0Kc2V1cltbInBlcmMubWl0byJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChzZXVyLCBwYXR0ZXJuID0gIl5tdC0iKSAjIyMgV2UgaW5jbHVkZSBhbGwgZ2VuZXMgc3RhcnRpbmcgd2l0aCAibXQtIiBmb3IgY2FsY3VsYXRpbmcgdGhpcyBRQyBzYXRpc3RpYy4gRGVwZW5kaW5nIG9uIHRoZSBvcmdhbmlzbSwgdGhlIHNlYXJjaCBwYXR0ZXJuIG1pZ2h0IG5lZWQgdG8gYmUgbW9kaWZpZWQsIGUuZy4gaW50byAiTVQtIiBmb3IgaHVtYW4Kc3VtbWFyeShzZXVyJHBlcmMubWl0bykKYGBgCgoKIyMjIyAtIFVNSSBjb3VudHMgcGVyIGNlbGwKYGBge3J9Cm91dGxpZXJzPWMoKQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIG91dGxpZXJzPWMob3V0bGllcnMsIAogICAgICAgICAgICAgc2NhdGVyOjppc091dGxpZXIoc2V1ciRuQ291bnRfUk5BW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKICAgICAgICAgICAgICkKfQpzZXVyJG5VTUkub3V0bGllci5sb3cgPC0gb3V0bGllcnNbY29sbmFtZXMoc2V1cildCmNhdCgiT3V0bGllcnM6IixzdW0oc2V1ciRuVU1JLm91dGxpZXIubG93KSkKYGBgCgpDcmVhdGUgaGlzdG9ncmFtcwpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLjUsIGZpZy53aWR0aCA9IDEwfQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIGhpc3Qoc2V1ciRuQ291bnRfUk5BW3NldXIkc2FtcGxlPT1pXSwKICAgICAgICBicmVha3MgPSAxMDAseGxhYj0ibkNvdW50X1JOQSIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIlRvdGFsIFVNSSBjb3VudHMgcGVyIGNlbGw6ICIsaSkpCiAgaWYoc3VtKHNldXIkc2FtcGxlPT1pICYgc2V1ciRuVU1JLm91dGxpZXIubG93KSE9MCkKICAgIGFibGluZSh2ID0gbWF4KHNldXIkbkNvdW50X1JOQVtzZXVyJHNhbXBsZT09aSAmIHNldXIkblVNSS5vdXRsaWVyLmxvd10pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgbkNvdW50X1JOQSkpICsgCiAgICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogICAgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjMsIGFlcyhjb2w9blVNSS5vdXRsaWVyLmxvdykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJUb3RhbCBVTUkgY291bnRzIHBlciBjZWxsOiAiLGkpKSkKfQpgYGAKCgojIyMjIC0gTnVtYmVyIG9mIGdlbmVzIHBlciBjZWxsCmBgYHtyfQpvdXRsaWVycz1jKCkKZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBvdXRsaWVycz1jKG91dGxpZXJzLCAKICAgICAgICAgICAgIHNjYXRlcjo6aXNPdXRsaWVyKHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKICAgICAgICAgICAgICkKfQpzZXVyJG5HZW5lLm91dGxpZXIubG93IDwtIG91dGxpZXJzW2NvbG5hbWVzKHNldXIpXQpjYXQoIk91dGxpZXJzOiIsc3VtKHNldXIkbkdlbmUub3V0bGllci5sb3cpKQpgYGAKQ3JlYXRlIGhpc3RvZ3JhbXMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBoaXN0KHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pXSwKICAgICAgICBicmVha3MgPSAxMDAseGxhYj0ibkNvdW50X1JOQSIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIk51bWJlciBvZiBnZW5lcyBwZXIgY2VsbDogIixpKSkKICBpZihzdW0oc2V1ciRzYW1wbGU9PWkgJiBzZXVyJG5HZW5lLm91dGxpZXIubG93KSE9MCkKICAgIGFibGluZSh2ID0gbWF4KHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pICYgc2V1ciRuR2VuZS5vdXRsaWVyLmxvd10pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgbkZlYXR1cmVfUk5BKSkgKyAKICAgIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICt0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1uR2VuZS5vdXRsaWVyLmxvdykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmBgYAoKIyMjIyAtIFByb3BvcnRpb24gb2YgbWl0b2hvbmRyaWFsIGdlbmVzIHBlciBjZWxsCmBgYHtyfQpvdXRsaWVycz1jKCkKZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBvdXRsaWVycz1jKG91dGxpZXJzLCAKICAgICAgICAgICAgIHNjYXRlcjo6aXNPdXRsaWVyKHNldXIkcGVyYy5taXRvW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0iaGlnaGVyIiwgbG9nPVRSVUUpCiAgICAgICAgICAgICApCn0Kc2V1ciRtaXRvLm91dGxpZXIuaGlnaCA8LSBvdXRsaWVyc1tjb2xuYW1lcyhzZXVyKV0KY2F0KCJPdXRsaWVyczoiLHN1bShzZXVyJG1pdG8ub3V0bGllci5oaWdoKSkKYGBgCkNyZWF0ZSBoaXN0b2dyYW1zCmBgYHtyICwgZmlnLmhlaWdodCA9IDMuNSwgZmlnLndpZHRoID0gMTB9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CiAgaGlzdChzZXVyJHBlcmMubWl0b1tzZXVyJHNhbXBsZT09aV0sCiAgICAgICAgYnJlYWtzID0gMTAwLHhsYWI9InBlcmMubWl0byIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIiUgbWl0byBnZW5lcyBwZXIgY2VsbDogIixpKSkKICBpZihzdW0oc2V1ciRzYW1wbGU9PWkgJiBzZXVyJG1pdG8ub3V0bGllci5oaWdoKSE9MCkKICAgIGFibGluZSh2ID0gbWluKHNldXIkcGVyYy5taXRvW3NldXIkc2FtcGxlPT1pICYgc2V1ciRtaXRvLm91dGxpZXIuaGlnaF0pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgcGVyYy5taXRvKSkgKyAKICAgIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICt0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1taXRvLm91dGxpZXIuaGlnaCkpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCIlIG1pdG8gZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmBgYAoKCgojIyMjIE92ZXJsYXAgb2YgY2VsbHMsIG91dGxpZXJzIGZvciBVTUkgY291bnRzLCBudW1iZXIgb2YgZ2VuZXMgYW5kICUgbWl0b2Nob2RyaWFsIGdlbmVzIHBlciBjZWxsCkEgbGFyZ2UgbnViZXIgb2YgY2VsbHMsIG91dGxpZXJzIHNwZWNpZmljYWxseSBmb3Igb25lIFFDIG1ldHJpYyBtaWdodCBiZSBhIGNvbmNlcm4gYW5kIHJlcXVpcmUgZnV1cmh0ZXIgaW52ZXN0aWdhdGlvbgpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLjUsIGZpZy53aWR0aCA9IDZ9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CnYgPC12ZW5uLmRpYWdyYW0oCiAgbGlzdCAoblVNST1jb2xuYW1lcyhzZXVyKVtzZXVyJG5VTUkub3V0bGllci5sb3cgJiBzZXVyJHNhbXBsZT09aV0sCiAgICAgICAgbkdlbmU9Y29sbmFtZXMoc2V1cilbc2V1ciRuR2VuZS5vdXRsaWVyLmxvdyAmIHNldXIkc2FtcGxlPT1pXSwKICAgIHBlcmMubWl0bz1jb2xuYW1lcyhzZXVyKVtzZXVyJG1pdG8ub3V0bGllci5oaWdoICYgc2V1ciRzYW1wbGU9PWldKSwKICBmaWxlbmFtZT1OVUxMLG1haW49aSwKICBhbHBoYSA9IGMoIDAuNSwwLjUsMC41KSwKICBmaWxsID0gYygiZ3JlZW4iLCJvcmFuZ2UiLCJibHVlIikKKQpncmlkLm5ld3BhZ2UoKQpncmlkLmRyYXcodikKcm0odikKfQpgYGAKCgojIyMjIEhvdyBtYW55IGNlbGxzIHdpbGwgYmUgcmVtb3ZlZCBkdXJpbmcgdGhlIGZpbHRlcmluZwpgYGB7cix3YXJuaW5nPUZBTFNFfQpjZWxscy50by5rZWVwPSAhIChzZXVyJG1pdG8ub3V0bGllci5oaWdoIHwgc2V1ciRuR2VuZS5vdXRsaWVyLmxvdyB8IHNldXIkblVNSS5vdXRsaWVyLmxvdykKcHJpbnQocGFzdGUobmNvbChzZXVyKS0gCiAgICAgICAgICAgIG5jb2woc2V1clssY2VsbHMudG8ua2VlcF0pLAogICAgICJjZWxscyB0byBiZSByZW1vdmVkIikpCmBgYAoKIyMjIyBWaW9saW4gcGxvdHMgYWZ0ZXIgZmlsdGVyaW5nCmBgYHtyICwgZmlnLmhlaWdodCA9IDMuNSwgZmlnLndpZHRoID0gOH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBwcmludChnZ3Bsb3QoYXMuZGF0YS5mcmFtZShzZXVyW1tdXVtjZWxscy50by5rZWVwJiBzZXVyJHNhbXBsZT09aSwgXSksIGFlcygxLCBuQ291bnRfUk5BKSkgKyAKICAgZ2VvbV92aW9saW4oZmlsbD0iZ3JheTgwIikgKyB0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW5VTUkub3V0bGllci5sb3cpKSArCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJUb3RhbCBVTUkgY291bnRzIHBlciBjZWxsOiAiLGkpKSkKfQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dWyBjZWxscy50by5rZWVwJiBzZXVyJHNhbXBsZT09aSwgXSksIGFlcygxLCBuRmVhdHVyZV9STkEpKSArIAogICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArIHRoZW1lX2NsYXNzaWMoKSsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSsKICAgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjMsIGFlcyhjb2w9bkdlbmUub3V0bGllci5sb3cpKSArCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CiAgcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoc2V1cltbXV1bY2VsbHMudG8ua2VlcCYgc2V1ciRzYW1wbGU9PWksIF0pLCBhZXMoMSwgcGVyYy5taXRvKSkgKyAKICAgZ2VvbV92aW9saW4oZmlsbD0iZ3JheTgwIikgKyB0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW1pdG8ub3V0bGllci5oaWdoKSkgKwogICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMEJGQzQiLCAiI0Y4NzY2RCIpKStnZ3RpdGxlKHBhc3RlMCgiJSBtaXRvIGdlbmVzIHBlciBjZWxsOiAiLGkpKSkKfQpgYGAKCiMjIyMgRmlsdGVyIHRoZSBvdXRsaWVyIGNlbGxzCmBgYHtyfQogIHNldXJfY2xlYW48LXNldXJbLCBjZWxscy50by5rZWVwXQogIHByaW50KGRpbShzZXVyKSkKICBwcmludChkaW0oc2V1cl9jbGVhbikpCmBgYAoKCiMjIyMgMi4yKSBHZXQgUENBIG91dGxpZXJzIChPcHRpb25hbCkKR2V0IGFkZGl0aW9uYWwgb3V0bGllcnMsIHVzaW5nIG11bHRpdmFyaWF0ZSBvdXRsaWVyIGRldGVjdGlvbiBmdW5jdGlvbiBvZiBzY2F0ZXIsIGJhc2VkIG9uIFBDQSBjb21wdXRlZCBvbiBRQyBtZXRyaWMgZGF0YSAodXNlcyB0aGUgbXZvdXRsaWVyIHBhY2thZ2UpCgpgYGB7cn0Kc2NlPWFzLlNpbmdsZUNlbGxFeHBlcmltZW50KHNldXJfY2xlYW4pCiMjIyMjIENob29zZSBRQyB2YXJpYWJsZXMgZm9yIGF1dGhvbWF0aWMgb3V0bGllciBkZXRlY3Rpb24Kc2VsZWN0ZWRfdmFyaWFibGVzIDwtIGMoIm5Db3VudF9STkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iKQpzZXRkaWZmKHNlbGVjdGVkX3ZhcmlhYmxlcywgY29sbmFtZXMoY29sRGF0YShzY2UpKSkgIyMjIGNoZWNrIGlmIGFueSBvZiB0aGUgc2VsZWN0ZWQgUUMgbWV0cmljcyBhcmUgbm90IHByZXNlbnQgaW4gdGhlIG1ldGFkYXRhCmBgYAojIyMjIyBEZXRlY3Qgb3V0bGllcnMKYGBge3J9Cm91dGxpZXJzPWMoKQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewpzY2Vfc2FtcGxlPXJ1bkNvbERhdGFQQ0Eoc2NlWyxzY2Ukc2FtcGxlPT1pXSwgb3V0bGllcnM9VCwgdmFyaWFibGVzPXNlbGVjdGVkX3ZhcmlhYmxlcykKb3V0bGllcnM9YyhvdXRsaWVycyxzY2Vfc2FtcGxlJG91dGxpZXIpCn0KYGBgCmBgYHtyfQpzY2Ukb3V0bGllcnM9b3V0bGllcnMKc2V1cl9jbGVhbiA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVyX2NsZWFuLCBtZXRhZGF0YSA9IGNvbERhdGEoc2NlKVtjb2xuYW1lcyhzZXVyX2NsZWFuKSwib3V0bGllcnMiXSwgY29sLm5hbWUgPSAicGNhLm91dGxpZXIiKQpjYXQoIk91dGxpZXJzOiIsc3VtKHNldXJfY2xlYW4kcGNhLm91dGxpZXIpKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBuQ291bnRfUk5BKSkgKyAKICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArIHRoZW1lX2NsYXNzaWMoKSsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSsKICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1vdXRsaWVycykpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMEJGQzQiLCAiI0Y4NzY2RCIpKStnZ3RpdGxlKHBhc3RlMCgiVG90YWwgVU1JIGNvdW50cyBwZXIgY2VsbDogIixpKSkpCn0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBuRmVhdHVyZV9STkEpKSArIAogIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICsgdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW91dGxpZXJzKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CiAgZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBwZXJjLm1pdG8pKSArIAogIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICsgdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW91dGxpZXJzKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCIlIG1pdG8gZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQogIH0KYGBgCgojIyMjIyBBcyB0aGUgbnVtYmVyIG9mIGFkZGl0aW9uYWwgUENBIG91dGxpZXJzIGlzIGxhcmdlLCB3ZSBrZWVwIHRob3NlIGNlbGxzIGZvciBub3cgYW5kIHdpbGwgY29uc2lkZXIgbGF0ZXIgaWYgdGhleSBuZWVkIHRvIGJlIHJlbW92ZWQgb3Igbm90CmBgYHtyfQojIyMgUmVtb3ZlIHRoZSBQQ0Egb3V0bGllciBjZWxscwojIHNldXJfY2xlYW48LXNldXJfY2xlYW5bLCBjZWxscy50by5rZWVwXQojIHByaW50KGRpbShzZXVyX2NsZWFuKSkKYGBgCgojIyMgMykgUUM6IEdFTkVTClRvIGRlZmluZSBhIGN1dG9mZiBvZiBsb3dseS1hYnVuZGFudCBnZW5lcywgd2UgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGxvZy1tZWFucyBhY3Jvc3MgYWxsIGdlbmVzLiBUaGUgY3V0b2ZmIGlzIHBsYWNlZCBpbiBtaWRkbGUgb2YgdGhlIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCBvZiB0aGUgZ3JhcGggYmVmb3JlIHRoZSBwZWFrLgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTB9CnRocmVzaG9sZHM8LWMoMC4wMDUsMC4wMDUpCmF2ZS5jb3VudHM9bGlzdCgpCmZvciAoIGkgaW4gMTogbGVuZ3RoKHVuaXF1ZShzZXVyX2NsZWFuJHNhbXBsZSkpKSB7CiAgIGF2ZS5jb3VudHNbW2ldXSA8LSByb3dNZWFucyhhcy5tYXRyaXgoR2V0QXNzYXlEYXRhKHNldXJfY2xlYW4sIHNsb3Q9ImNvdW50cyIpWyxzZXVyX2NsZWFuJHNhbXBsZT09dW5pcXVlKHNldXIkc2FtcGxlKVtpXV0pKQogIGhpc3QobG9nMTAoYXZlLmNvdW50c1tbaV1dKSwgYnJlYWtzPTEwMCwgbWFpbj1wYXN0ZTAoIkhpc3RvZ3JhbSBvZiBtZWFuIFVNSSBjb3VudHMgcGVyIGdlbmU6ICIsdW5pcXVlKHNldXIkc2FtcGxlKVtpXSksIGNvbD0iZ3JleTgwIiwKICAgICB4bGFiPWV4cHJlc3Npb24oTG9nWzEwXX4ibWVhbiBjb3VudCBwZXIgZ2VuZSIpKQogIGFibGluZSh2PWxvZzEwKHRocmVzaG9sZHNbaV0pLCBjb2w9ImJsdWUiLCBsd2Q9MiwgbHR5PTIpCn0KYGBgCgpOdW1iZXIgb2YgZ2VuZXMgdG8ga2VlcApgYGB7cix3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQp1c2VnZW5lcz1saXN0KCkKZm9yICggaSBpbiAxOiBsZW5ndGgodW5pcXVlKHNldXJfY2xlYW4kc2FtcGxlKSkpIHsKICAgIHByaW50KHVuaXF1ZShzZXVyJHNhbXBsZSlbaV0pCiAgICB1c2VnZW5lc1tbaV1dPC1hdmUuY291bnRzW1tpXV0+dGhyZXNob2xkc1tpXQogICAgcHJpbnQodGFibGUodXNlZ2VuZXNbW2ldXSkpCn0KYGBgCgojIyMjIyBGaWx0ZXIgb3V0IHRoZSBsb3dseS1hYnVuZGFudCBnZW5lcyB0aGF0IG92ZXJsYXAgYmV0d2VlbiBhbGwgc2FtcGxlcwpgYGB7cn0KaT0xCmdlbmVzLmZpbHRlcj1uYW1lcyh1c2VnZW5lc1tbaV1dWyEgdXNlZ2VuZXNbW2ldXV0pCmZvciAoIGkgaW4gMjogbGVuZ3RoKHVuaXF1ZShzZXVyX2NsZWFuJHNhbXBsZSkpKSB7CiAgZ2VuZXMuZmlsdGVyPWludGVyc2VjdChnZW5lcy5maWx0ZXIsIG5hbWVzKHVzZWdlbmVzW1tpXV1bISB1c2VnZW5lc1tbaV1dXSkpCn0KdXNlZ2VuZXMuZmluYWw9IXJvd25hbWVzKHNldXJfY2xlYW4pICVpbiUgZ2VuZXMuZmlsdGVyCnRhYmxlKHVzZWdlbmVzLmZpbmFsKQpgYGAKYGBge3J9CnNldXJfY2xlYW48LXNldXJfY2xlYW5bdXNlZ2VuZXMuZmluYWwsIF0KYGBgCgojIyMgNCkgQ0FMQ1VMQVRFIERPVUJMRVQgU0NPUkUKV2UgZXN0aW1hdGUgZG91YmxldCBzY29yZSBwZXIgY2VsbCwgdXNpbmcgdGhlIHNjRGJsRmluZGVyIHBhY2thZ2Usd2hpY2ggc2ltdWxhdGVzIGFydGlmaWNpYWwgZG91YmxldHMgZnJvbSBjZWxsIGNsdXN0ZXJzLgpJdCBpcyBwcmVmZXJhYmxlIHRvIGxvb2sgZm9yIGRvdWJsZXRzIHNlcGFyYXRlbHkgZm9yIGVhY2ggc2FtcGxlCmBgYHtyfQpkb3VibGV0LnNjb3JlPC0gc2NEYmxGaW5kZXI6OnNjRGJsRmluZGVyKGFzLlNpbmdsZUNlbGxFeHBlcmltZW50KHNldXJfY2xlYW4pLCBzYW1wbGVzPSJzYW1wbGUiLCBCUFBBUkFNPQpCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKDMpLHJldHVyblR5cGU9InRhYmxlIikgIyMjIFVzaW5nIDMgY29yZXMKc2V1cl9jbGVhbiA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVyX2NsZWFuLCBtZXRhZGF0YSA9IGRvdWJsZXQuc2NvcmVbY29sbmFtZXMoc2V1cl9jbGVhbiksInNjb3JlIl0sIGNvbC5uYW1lID0gImRvdWJsZXQuc2NvcmUiKQpzZXVyX2NsZWFuIDwtIEFkZE1ldGFEYXRhKG9iamVjdCA9IHNldXJfY2xlYW4sIG1ldGFkYXRhID0gZG91YmxldC5zY29yZVtjb2xuYW1lcyhzZXVyX2NsZWFuKSwiY2xhc3MiXSwgY29sLm5hbWUgPSAiZG91YmxldC5jbGFzcyIpCmBgYApgYGB7ciB9CnRhYmxlKHNldXJfY2xlYW4kZG91YmxldC5jbGFzcykKYGBgCmBgYHtyIH0Kc3VtbWFyeShzZXVyX2NsZWFuJGRvdWJsZXQuc2NvcmUpCmBgYAoKIyMjIDUpIE5PUk1BTElaQVRJT04sIEhWRyBERVRFQ1RJT04gYW5kIFBDQQpgYGB7cn0KIyMjIyMgTm9ybWFsaXplIGRhdGEKc2V1cl9jbGVhbiA8LSBOb3JtYWxpemVEYXRhKHNldXJfY2xlYW4sdmVyYm9zZSA9IEYpCiMjIyMjIEhWRyBkZXRlY3Rpb24gCnNldXJfY2xlYW4gPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoc2V1cl9jbGVhbix2ZXJib3NlPUYpCiMjIyMjIFNjYWxlIGRhdGEgcGVyIGdlbmUKc2V1cl9jbGVhbiA8LSBTY2FsZURhdGEoc2V1cl9jbGVhbix2ZXJib3NlPUYpCiMjIyMjIFBDQQpzZXVyX2NsZWFuIDwtIFJ1blBDQShzZXVyX2NsZWFuLCBmZWF0dXJlcyA9VmFyaWFibGVGZWF0dXJlcyhzZXVyX2NsZWFuKSAsdmVyYm9zZT1GKQpgYGAKIyMjIyBTZWxlY3QgUENzIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzIG9mIHRoZSBkYXRhc2V0ClNldXJhdCBwcm92aWRlcyBhIGhldXJpc3RpYyBtZXRob2QgdG8gaGVscCB1cyBzZWxlY3QgUEMgY29tcG9uZW50cy4gSXQgZ2VuZXJhdGVzIGEgcmFua2luZyBvZiBQQ3MgYmFzZWQgb24gdGhlIHBlcmNlbnRhZ2Ugb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggb2YgdGhlbSAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNix3YXJuaW5nPUZBTFNFfQpFbGJvd1Bsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbixuZGltcyA9NTApCmBgYApIZWF0bWFwIG9mIHRoZSBnZW5lcyB0aGF0IGRyaXZlIGVhY2ggUEMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMTUsIGZpZy53aWR0aCA9IDEyLHdhcm5pbmc9RkFMU0V9CkRpbUhlYXRtYXAoc2V1cl9jbGVhbiwgZGltcyA9IDE6MzAsIGNlbGxzID0gNTAwMCwgYmFsYW5jZWQgPSBUUlVFKQpgYGAKCiMjIyA2KSBVTUFQIERJTUVOU0lPTkFMSVRZIFJFRFVDVElPTgpSdW4gVW5pZm9ybSBNYW5pZm9sZCBBcHByb3hpbWF0aW9uIGFuZCBQcm9qZWN0aW9uIChVTUFQKSBkaW1lbnNpb25hbCByZWR1Y3Rpb24gdGVjaG5pcXVlIGZvciB2aXN1YWxpc2F0aW9uIG9mIHRoZSBkYXRhCgpgYGB7cn0KIyMjIFBDIHNlbGVjdGlvbiBmb3IgZG93bnN0cmVhbSBhbmFseXNpcwpkaW1zLnVzZTwtMzAKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpzZXVyX2NsZWFuIDwtIFJ1blVNQVAoc2V1cl9jbGVhbiwgZGltcyA9IDE6ZGltcy51c2UsIHZlcmJvc2U9RikKYGBgCgpWaXN1YWxpc2UgdGhlIFVNQVAgcGxvdCwgY29sb3VyZWQgYnkgc2FtcGxlIHRvIGNoZWNrIGZvciBiYXRjaCBlZmZlY3RzCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDd9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gN30KRmVhdHVyZVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZmVhdHVyZXMgPSBjKCJMeTZjMiIsICJDY3IyIiksIHNwbGl0LmJ5ID0gInNhbXBsZSIgKQpgYGAKVGhlIFVNQVAgcGxvdCBzaG93cyByZWxhdGl2ZWx5IGdvb2QgbWl4aW5nIGJ5IHNhbXBsZSBmb3IgbW9zdCBjZWxscy4gTG9naWNhbGx5LCBtb25vY3l0ZXMgYXJlIG1haW5seSBwcmVzZW50IGluIENjcjIgV1QgY2VsbHMsIGFzIHZpc3VhbGl6ZWQgYnkgQ2NyMiBhbmQgTHk2YzIgZXhwcmVzc2lvbi4KCiMjIyA3KSBCQVRDSCBDT1JSRUNUSU9OIApJbiBjYXNlIGJhdGNoIGNvcnJlY3Rpb24gaXMgbmVjZXNzYXJ5LCBpdCBjYW4gYmUgcGVyZm9ybWVkIHVzaW5nIHRoZSBoYXJtb255IHBhY2thZ2UuIFNlbGVjdCBhIHZhbHVlIGZvciB0aGV0YSAtIGRpdmVyc2l0eSBjbHVzdGVyaW5nIHBlbmFsdHkgcGFyYW1ldGVyIChEZWZhdWx0IHRoZXRhPTIpLiBMYXJnZXIgdmFsdWVzIG9mIHRoZXRhIHJlc3VsdCBpbiBzdHJvbmdlciBpbnRlZ3JhdGlvbiwgYnV0IGNhbiBsZWFkIHRvIG92ZXItY29ycmVjdGlvbikKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNH0KdGhldGEudXNlPC0xICMgSGVyZSB0aGUgdGhldGEgcGFyYW1ldGVyIGNhbiBiZSBtb2RpZmllZApzZXVyX2NsZWFuPC1SdW5IYXJtb255KHNldXJfY2xlYW4sIGdyb3VwLmJ5LnZhcnM9InNhbXBsZSIsdGhldGEgPXRoZXRhLnVzZSwgIAogICAgICAgICAgICAgICAgICAgICAgIHBsb3RfY29udmVyZ2VuY2UgPSBUUlVFLCByZWR1Y3Rpb24uc2F2ZSA9cGFzdGUwKCJoYXJtb255VGhldGEiLHRoZXRhLnVzZSksCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmtleSA9IHBhc3RlMCgiaGFybW9ueVRoZXRhIix0aGV0YS51c2UsIl8iKSwgdmVyYm9zZT1GKSAKc2V1cl9jbGVhbiA8LSBSdW5VTUFQKHNldXJfY2xlYW4scmVkdWN0aW9uID1wYXN0ZTAoImhhcm1vbnlUaGV0YSIsdGhldGEudXNlKSwgCiAgICAgICAgICAgICAgICAgICAgICBkaW1zID0gMTpkaW1zLnVzZSx2ZXJib3NlID1GLCAgIHJlZHVjdGlvbi5uYW1lID0gcGFzdGUwKCJ1bWFwSGFybW9ueVRoZXRhIix0aGV0YS51c2UsICJQQyIsZGltcy51c2UpLCAKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5rZXkgPSBwYXN0ZTAoInVtYXBIYXJtb255VGhldGEiLHRoZXRhLnVzZSwgIlBDIixkaW1zLnVzZSwiXyIpKSAgCmBgYAoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gN30KIyMjIFZpc3VhbGlzZSB0aGUgaGFybW9ueS1jb3JyZWN0ZWQgVU1BUCBwbG90LCBjb2xvdXJlZCBieSBzYW1wbGUgdG8gaW5zcGVjdCBpZiB0aGUgYmF0Y2ggZWZmZWN0cyB3ZXJlIHJlc29sdmVkCiBEaW1QbG90KG9iamVjdCA9IHNldXJfY2xlYW4scmVkdWN0aW9uID0gcGFzdGUwKCJ1bWFwSGFybW9ueVRoZXRhIix0aGV0YS51c2UsICJQQyIsZGltcy51c2UpLCAKICAgICAgICAgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKStnZ3RpdGxlKHBhc3RlMCgiU2FtcGxlczogVU1BUCBIYXJtb255IHRoZXRhPSAiLHRoZXRhLnVzZSwgIiBQQz0gIixkaW1zLnVzZSkpCmBgYApBcyB0aGUgdGhlIHNhbXBsZXMgYWxyZWFkeSBzaG93IGdvb2QgbWl4aW5nIGJlZm9yZSBiYXRjaCBjb3JyZWN0aW9uLCBhbmQgdGhlIGxpYnJhcnkgcHJlcGFyYXRpb24gd2FzIGRvbmUgb24gdGhlIHNhbWUgZGF5IGZvciBib3RoIHNhbXBsZXMsIHdlIGFyZSBnb2luZyB0byBjb250aW51ZSB3aXRob3V0IGJhdGNoIGNvcnJlY3Rpb24KCgojIyMjIFZpc3VhbGl6ZSBRQyBtZXRyaWNzCmBgYHtyICwgZmlnLmhlaWdodCA9OCwgZmlnLndpZHRoID0gMTAsd2FybmluZz1GQUxTRX0KRmVhdHVyZVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwKICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iLCJkb3VibGV0LnNjb3JlIikpCmBgYApWaXN1YWxpemUgUENBIG91dGxpZXIgY2VsbHMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID01LHdhcm5pbmc9RkFMU0V9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZ3JvdXAuYnkgPSAicGNhLm91dGxpZXIiKSAKYGBgCiMjIyMgVmlzdWFsaXplIFFDIG1ldHJpY3MgZm9yIHBjYSBvdXRsaWVycywgYW5kIHRoZSByZW1haW5pbmcgY2VsbHMKYGBge3IgLCBmaWcuaGVpZ2h0ID0xMCwgZmlnLndpZHRoID03LHdhcm5pbmc9RkFMU0V9CkZlYXR1cmVQbG90KHNldXJfY2xlYW4sIHNwbGl0LmJ5ID0icGNhLm91dGxpZXIiICxmZWF0dXJlcz1jKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iLCAiTWtpNjciLCJTdG1uMSIpLCAKa2VlcC5zY2FsZT0iZmVhdHVyZSIsIHB0LnNpemUgPSAwLjIpCmBgYApUaGUgUENBIG91dGxpZXJzIGluY2x1ZGUgY2VsbHMgd2l0aCBoaWdoIHBlcmNlbnQgbWl0b2Nob25kcmlhbCBnZW5lcywgYXMgd2VsbCBhcyBjZWxscyB3aXRoIGhpZ2ggVU1JIGNvbnRlbnQgYW5kIG51bWJlciBvZiBnZW5lcy4gTWFueSBvZiB0aGUgbGF0dGVyIGV4cHJlc3MgcHJvbGlmZXJhdGlvbiBtYXJrZXJzIChNa2k2NywgU3RtbjEpLCB0aGUgYWN0aXZlIHByb2xpZmVyYXRpb24gZXhwbGFpbmluZyB0aGVpciBoaWdoZXIgbnVtYmVyIG9mIFVNSXMgYW5kIGdlbmVzLiBUaGVyZWZvcmUgdGhlIFBDQSBvdXRsaWVycyB3aWxsIG5vdCBiZSByZW1vdmVkLgoKCgojIyMgOCkgQ0xVU1RFUklORwpXZSBydW4gTG91dmFpbiBjbHVzdGVyaW5nIHdpdGggdmFyeWluZyB0aGUgY2x1c3RlcmluZyByZXNvbHV0aW9uIGJldHdlZW4gMCBhbmQgMgpgYGB7cn0Kc2V1cl9jbGVhbiA8LSBGaW5kTmVpZ2hib3JzKHNldXJfY2xlYW4sIGRpbXMgPSAxOmRpbXMudXNlLCAKICAgICAgICAgICAgICAgICAgICAgIGdyYXBoLm5hbWUgPSBwYXN0ZTAoIlJOQV9zbm5fUEMiLGRpbXMudXNlKSwgdmVyYm9zZT1GKQpmb3IgKCBpIGluIHNlcSgwLDIsIDAuMjUpKQogIHNldXJfY2xlYW4gPC0gRmluZENsdXN0ZXJzKHNldXJfY2xlYW4sIHJlc29sdXRpb24gPSBpLCBncmFwaC5uYW1lPXBhc3RlMCgiUk5BX3Nubl9QQyIsZGltcy51c2UpLCB2ZXJib3NlPUYpCmBgYAoKSWYgdGhlIGJhdGNoIGNvcnJlY3Rpb24gaXMgbmVjZXNzYXJ5IGZvciBhIHNwZWNpZmljIGRhdGFzZXQsIGNsdXN0ZXJpbmcgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGUgaGFybW9ueS1jb3JyZWN0ZWQgUENBIGVtYmVkZGluZ3M6CmBgYHtyfQojc2V1cl9jbGVhbiA8LSBGaW5kTmVpZ2hib3JzKHNldXJfY2xlYW4sIGRpbXMgPSAxOmRpbXMudXNlLCByZWR1Y3Rpb24gPXBhc3RlMCgiaGFybW9ueVRoZXRhIix0aGV0YS51c2UpLCBncmFwaC5uYW1lID0gcGFzdGUwKCJSTkFfc25uX2hhcm1vbnlfdGhldGEiLHRoZXRhLnVzZSwgIi5QQyIsZGltcy51c2UpLCB2ZXJib3NlPUYpCiNmb3IgKCBpIGluIHNlcSgwLDIsIDAuMjUpKQojICBzZXVyX2NsZWFuIDwtIEZpbmRDbHVzdGVycyhzZXVyX2NsZWFuLCByZXNvbHV0aW9uID0gaSwgZ3JhcGgubmFtZT1wYXN0ZTAoIlJOQV9zbm5faGFybW9ueV90aGV0YSIsdGhldGEudXNlLCAiLlBDIixkaW1zLnVzZSksIHZlcmJvc2U9RikKYGBgCgojIyMjIENob29zaW5nIGEgY2x1c3RlcmluZyByZXNvbHV0aW9uIHZhbHVlClBsb3Qgb2YgYSBjbHVzdGVyaW5nIHRyZWUgc2hvd2luZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gY2x1c3RlcmluZyBhdCBkaWZmZXJlbnQgcmVzb2x1dGlvbiAodXNpbmcgdGhlIGNsdXN0cmVlIHBhY2thZ2UpLlRoaXMgcGxvdCBhbGxvd3MgdXMgdG8gc2VlIGhvdyBhcmUgdGhlIGNsdXN0ZXJzIHJlbGF0ZWQgdG8gZWFjaCBvdGhlciBhbmQgd2hpY2ggb25lcyBhcmUgc3RhYmxlIGFjcm9zcyBkaWZmZXJlbnQgcmVzb2x1dGlvbnMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTB9CmNsdXN0cmVlKHNldXJfY2xlYW4sIHByZWZpeCA9IHBhc3RlMCgiUk5BX3Nubl9QQyIsZGltcy51c2UsIl9yZXMuIikpKwogIGdndGl0bGUocGFzdGUoIlBDID0iLGRpbXMudXNlKSkKYGBgClRoZSBjbHVzdGVycyBhcmUgcmVsYXRpdmVseSBzdGFibGUgdW50aWwgcmVzb2x1dGlvbiBvZiAxCgojIyMjIFZpc3VhbGl6ZSB0aGUgY2x1c3RlcnMgZm9yIHNldmVyYWwgcmVzb2x1dGlvbiB2YWx1ZXMgb24gdGhlIFVNQVAgcGxvdApgYGB7ciAsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMn0KcGxvdDwtbGlzdCgpCmZvciAoIHJlcyBpbiBjKDAuMjUsMC41LCAwLjc1LDEpKQogIHBsb3RbW2FzLmNoYXJhY3RlcihyZXMpXV08LURpbVBsb3Qoc2V1cl9jbGVhbixsYWJlbD1ULHJlcGVsPVQsIGdyb3VwLmJ5ID0gcGFzdGUwKCJSTkFfc25uX1BDIixkaW1zLnVzZSwiX3Jlcy4iLHJlcykpKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdndGl0bGUocGFzdGUoIlBDID0iLGRpbXMudXNlLCJyZXM9IixyZXMpKQpwbG90X2dyaWQocGxvdGxpc3Q9cGxvdCkKYGBgCgojIyMgIDkpIEFOTk9UQVRJTkcgVEhFIENMVVNURVJTCkxldCdzIGZpbmQgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIChERSkgZ2VuZXMgcGVyIGNsdXN0ZXIgZm9yIHJlc29sdXRpb249MS4gCmBgYHtyfQpyZXM9MQpJZGVudHMoc2V1cl9jbGVhbik9ICBwYXN0ZTAoIlJOQV9zbm5fUEMiLGRpbXMudXNlLCJfcmVzLiIscmVzKQpJZGVudHMoc2V1cl9jbGVhbik9ICBmYWN0b3IoSWRlbnRzKHNldXJfY2xlYW4pLGxldmVscyA9IDA6KGxlbmd0aCh1bmlxdWUoSWRlbnRzKHNldXJfY2xlYW4pKSktMSkpCkRFZ2VuZXM8LUZpbmRBbGxNYXJrZXJzKHNldXJfY2xlYW4sbWluLmNlbGxzLmdyb3VwPTIscHNldWRvY291bnQudXNlID0gMC4wMSwgbWF4LmNlbGxzLnBlci5pZGVudCA9IDEwMDAsdmVyYm9zZSA9IEYpICMjIEJ5IHVzaW5nIGEgc21hbGxlciBwc2V1ZG9jb3VudC51c2UgdGhhbiB0aGUgZGVmYXVsdCBvZiAxLCB0aGUgYXZnbG9nRkMgdmFsdWVzIGNvcnJlc3BvbmQgbW9yZSBjbG9zZWx5IHRvIHRoZSBhY3R1YWwgbG9nIGZvbGQgY2hhbmdlcywgYW5kIGxlc3Mgd2VpZ2h0IGlzIGdpdmVuIHRvIGhpZ2hlciBleHByZXNzZWQgZ2VuZXMgdmVyc3VzIGxvd2x5IGV4cHJlc3NlZCBvbmVzLgpgYGAKCiMjIyMgTmV4dCx3ZSB2aXN1YWxpc2UgdGhlIGV4cHJlc3Npb24gb2YgdGhlIHRvcCBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggY2x1c3RlciBpbiBhIGhlYXRtYXAKYGBge3J9CmZlYXR1cmVzLnBsb3Q9REVnZW5lcyU+JWZpbHRlcihhdmdfbG9nRkM+MCklPiUgZ3JvdXBfYnkoY2x1c3RlciklPiV0b3BfbihuPS0zLHd0PXBfdmFsX2FkaiklPiVwdWxsKGdlbmUpICMjIyB0b3AgMyBtYXJrZXJzCmxlbmd0aChmZWF0dXJlcy5wbG90KQpgYGAKYGBge3J9CnNldXJfY2xlYW49U2NhbGVEYXRhKHNldXJfY2xlYW4sIGZlYXR1cmVzID0gcm93bmFtZXMoc2V1cl9jbGVhbikpICMjIyBTY2FsZSB0aGUgZGF0YSBmb3IgYWxsIGdlbmVzIHRvIGJlIGFibGUgdG8gdmlzdWFsaXNlIHRoZWlyIGV4cHJlc3Npb24gd2l0aCBEb0hlYXRtYXAKYGBgCmBgYHtyICwgZmlnLmhlaWdodCA9MTIsIGZpZy53aWR0aCA9MTJ9CkRvSGVhdG1hcChzZXVyX2NsZWFuLCBmZWF0dXJlcyA9IGZlYXR1cmVzLnBsb3QsIGFzc2F5ID0gIlJOQSIsIGFuZ2xlID0gOTAsIGxhYmVsID1ULCBzaXplPTQpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsaGlnaCA9ICJyZWQiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgoKCiMjIyMgQ2hlY2sgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIGNsdXN0ZXIgcGVyIHNhbXBsZQpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLCBmaWcud2lkdGggPSA4LHdhcm5pbmc9RkFMU0V9CmRhdGE8LWFzLmRhdGEuZnJhbWUodGFibGUoc2V1cl9jbGVhbiRzYW1wbGUsIElkZW50cyhzZXVyX2NsZWFuKSkpCmNvbG5hbWVzKGRhdGEpPWMoInNhbXBsZSIsImNsdXN0ZXIiLCJGcmVxIikKZ2dwbG90KGRhdGEsIGFlcyh4PWNsdXN0ZXIsIHk9RnJlcSwgZmlsbD1zYW1wbGUpKStnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikrZ2d0aXRsZShwYXN0ZTAoIk51bWJlciBvZiBjZWxscyBwZXIgY2x1c3RlciAocmVzb2x1dGlvbiA9IixyZXMsIikiKSkrdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMjIyBWaXN1YWxpc2UgdGhlIGV4cHJlc3Npb24gb2YgbWFya2VyIGdlbmVzIG9uIGEgVU1BUCBwbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGggPTExLHdhcm5pbmc9RkFMU0V9CkZlYXR1cmVQbG90KHNldXJfY2xlYW4sZmVhdHVyZXM9YygiQ2NyMiIsIkx5NmMyIiwiQzFxYSIsIlNwYXJjIiwiUDJyeTEyIiwiVGdmYmkiLCJNczRhNyIsIk1raTY3IiwiR2F0YTIiLCJIZGMiLCJDcGEzIiwiUzEwMGE5IiwiQ3NmM3IiLCJGbHQzIiwiWGNyMSIsIkNkMjA5YSIsIkgyLURNYjIiLCJDY3I3IiwiQ2NyOSIsIk1zNGExIiwiSmNoYWluIiwgIkNkM2UiLCJDZDhhIiwiQ2Q0IiwiRm94cDMiLCJLbHJiMWMiLCJTb3gyIiwiUGxwMSIpKQpgYGAKYGBge3IgfQpzZXVyX2NsZWFuJG5Db3VudF9STkFfbG9nPWxvZzIoc2V1cl9jbGVhbiRuQ291bnRfUk5BKQpzZXVyX2NsZWFuJG5GZWF0dXJlX1JOQV9sb2c9bG9nMihzZXVyX2NsZWFuJG5GZWF0dXJlX1JOQSkKYGBgCgojIyMjIFZpc3VhbGlzZSB0aGUgZXhwcmVzc2lvbiBvZiBtYXJrZXIgZ2VuZXMgdXNpbmcgYSBEb3RQbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9MTMsd2FybmluZz1GQUxTRX0KRG90UGxvdChzZXVyX2NsZWFuLGZlYXR1cmVzPWMoIlB0cHJjIiwiQ2NyMiIsIkx5NmMyIiwiUGxhYzgiLCJDMXFhIiwiU3BhcmMiLCJTYWxsMSIsIlRnZmJpIiwiTXM0YTciLCJNa2k2NyIsIlN0bW4xIiwiTWNtMyIsIklmaXQzIiwiTHk2ZyIsIkNzZjNyIiwiUzEwMGE5IiwiUzEwMGE4IiwiRmx0MyIsIlhjcjEiLCJDZDIwOWEiLCJIMi1ETWIyIiwiTmFwc2EiLCJTaWdsZWNoIiwiQ2NyOSIsIkNjcjciLCJDYWNuYjMiLCJDY2wyMiIsICJDZDNlIiwiQ2Q4YSIsIkNkNCIsIkxlZjEiLCJUY2Y3IiwiRm94cDMiLCJJbDJyYSIsIlRjcmctQzEiLCJUcmRjIiwiS2xyYjFjIiwiTmNyMSIsIkdubHkiLCAiQ2QxOSIsIk1zNGExIiwiU2RjMSIsIkpjaGFpbiIsIk1zNGEyIiwgIkdhdGEyIiwiQ2QyMDByMyIsIkNkaDEiLCJIZ2YiLCJIZGMiLCJNY3B0MSIsIkNwYTMiLCJTb3gyIiwiR2pjMyIsIlBscDEiLCAiZG91YmxldC5zY29yZSIsInBlcmMubWl0byIsIm5Db3VudF9STkFfbG9nIiwibkZlYXR1cmVfUk5BX2xvZyIpKStSb3RhdGVkQXhpcygpCmBgYAoKIyMjIyBBc3NpZ24gY2VsbCB0eXBlcyB0byB0aGUgY2x1c3RlcnMsIGJhc2VkIG9uIHRoZSBtYXJrZXIgZ2VuZSBleHByZXNzaW9uCmBgYHtyLHdhcm5pbmc9RkFMU0V9CklkZW50cyhzZXVyX2NsZWFuKSA8LSBwbHlyOjptYXB2YWx1ZXMoeCA9IElkZW50cyhzZXVyX2NsZWFuKSwgZnJvbSA9IDA6MjUsIAogICAgIHRvID1jKCJUQU0gMSIsIkNEOCBUIGNlbGxzIiwiVEFNIDIiLCJUQU0gMyIsIlRBTSA0IiwiQXJ0ZWZhY3QiLCJOSyBjZWxscyIsImNEQzIiLCJCYXNvcGhpbHMiLCJUcmVnIGNlbGxzIiwiVEFNIFByb2xpZmVyYXRpbmciLCJtaWdEQyIsIk1vbm9jeXRlcyIsImNEQzEiLCJUIGNlbGxzIFByb2xpZmVyYXRpbmciLCJDRDQgVCBjZWxscyAxIiwicERDIiwiQ0Q0IFQgY2VsbHMgMiIsIlRBTSA1IiwiUGxhc21hIGNlbGxzIiwiQiBjZWxscyIsIlRBTS9UIGRvdWJsZXRzIiwiTWFzdCBjZWxscyIsIk5ldXRyb3BoaWxzIiwiT2xpZ29kZW5kcm9jeXRlcyIsIlRBTS9CYXNvcGhpbCBkb3VibGV0cyIpKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsd2FybmluZz1GQUxTRX0KRGltUGxvdChzZXVyX2NsZWFuLHJlcGVsID1ULGxhYmVsPVQpIApgYGAKCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9MTQsd2FybmluZz1GQUxTRX0KRG90UGxvdChzZXVyX2NsZWFuLGZlYXR1cmVzPWMoIlB0cHJjIiwiQ2NyMiIsIkx5NmMyIiwiUGxhYzgiLCJDMXFhIiwiU3BhcmMiLCJTYWxsMSIsIlRnZmJpIiwiTXM0YTciLCJNa2k2NyIsIlN0bW4xIiwiTWNtMyIsIklmaXQzIiwiTHk2ZyIsIkNzZjNyIiwiUzEwMGE5IiwiUzEwMGE4IiwiRmx0MyIsIlhjcjEiLCJDZDIwOWEiLCJIMi1ETWIyIiwiTmFwc2EiLCJTaWdsZWNoIiwiQ2NyOSIsIkNjcjciLCJDYWNuYjMiLCJDY2wyMiIsICJDZDNlIiwiQ2Q4YSIsIkNkNCIsIkxlZjEiLCJUY2Y3IiwiRm94cDMiLCJJbDJyYSIsIlRjcmctQzEiLCJUcmRjIiwiS2xyYjFjIiwiTmNyMSIsIkdubHkiLCAiQ2QxOSIsIk1zNGExIiwiU2RjMSIsIkpjaGFpbiIsIk1zNGEyIiwgIkdhdGEyIiwiQ2QyMDByMyIsIkNkaDEiLCJIZ2YiLCJIZGMiLCJNY3B0MSIsIkNwYTMiLCJTb3gyIiwiR2pjMyIsIlBscDEiLCAiZG91YmxldC5zY29yZSIsInBlcmMubWl0byIsIm5Db3VudF9STkFfbG9nIiwibkZlYXR1cmVfUk5BX2xvZyIpKStSb3RhdGVkQXhpcygpCmBgYAoKIyMjICAxMCkgUkVNT1ZFIERPVUJMRVRTIEFORCBBUlRFRkFDVFMKUmVtb3ZlIHRoZSBkb3VibGV0cyBhbmQgYXJ0ZWZhY3RzICAtIGNsdXN0ZXIgd2l0aCBoaWdoIG1pdG9jaG9kcmlhbCBjb250ZW50IGFuZCB0aGUgb2xpZ29kZW5kcm9jeXRlcyBhcyB0aGUgbGF0dGVyIGFyZSBwcm9iYWJseSBkdWVzIHRvIGltcHVyaXRpZXMgZHVyaW5nIHRoZSBDRDQ1KyBzb3J0aW5nCgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gOCx3YXJuaW5nPUZBTFNFfQpkYXRhPC1hcy5kYXRhLmZyYW1lKHRhYmxlKHNldXJfY2xlYW4kZG91YmxldC5jbGFzcywgSWRlbnRzKHNldXJfY2xlYW4pKSkKY29sbmFtZXMoZGF0YSk9YygiZG91YmxldC5jbGFzcyIsImNsdXN0ZXIiLCJGcmVxIikKZ2dwbG90KGRhdGEsIGFlcyh4PWNsdXN0ZXIsIHk9RnJlcSwgZmlsbD1kb3VibGV0LmNsYXNzKSkrZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgY2VsbHMgcGVyIGNsdXN0ZXIgKHJlc29sdXRpb24gPSIscmVzLCIpIikpK3RoZW1lX2NsYXNzaWMoKStSb3RhdGVkQXhpcygpCmBgYApgYGB7cn0KY2VsbHMua2VlcD1XaGljaENlbGxzKHNldXJfY2xlYW4sIGlkZW50cyA9YygiQXJ0ZWZhY3QiLCJUQU0vVCBkb3VibGV0cyIsIlRBTS9CYXNvcGhpbCBkb3VibGV0cyIsIk9saWdvZGVuZHJvY3l0ZXMiKSwgaW52ZXJ0ID0gVCApCmNhdChuY29sKHNldXJfY2xlYW4pLWxlbmd0aChjZWxscy5rZWVwKSwiY2VsbHMgdG8gYmUgcmVtb3ZlZCIpCmBgYApSZW1vdmUgY2VsbHMgYW5kIHJlcnVuIEhWRyBkZXRlY3Rpb24sUENBIGFuZCBVTUFQCmBgYHtyfQpzZXVyX2FydGVmYWN0c19yZW1vdmVkPXNldXJfY2xlYW5bLGNlbGxzLmtlZXBdCnNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCx2ZXJib3NlPUYpCnNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQgPC0gU2NhbGVEYXRhKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsdmVyYm9zZT1GKQpzZXVyX2FydGVmYWN0c19yZW1vdmVkIDwtIFJ1blBDQShzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBmZWF0dXJlcyA9VmFyaWFibGVGZWF0dXJlcyhzZXVyX2FydGVmYWN0c19yZW1vdmVkKSAsdmVyYm9zZT1GKQpgYGAKCmBgYHtyICwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDYsd2FybmluZz1GQUxTRX0KIyMjIyBTZWxlY3QgUENzIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzIG9mIHRoZSBkYXRhc2V0CkVsYm93UGxvdChvYmplY3QgPSBzZXVyX2FydGVmYWN0c19yZW1vdmVkLG5kaW1zID01MCkKYGBgCkhlYXRtYXAgb2YgdGhlIGdlbmVzIHRoYXQgZHJpdmUgZWFjaCBQQwpgYGB7ciAsIGZpZy5oZWlnaHQgPSAxNSwgZmlnLndpZHRoID0gMTIsd2FybmluZz1GQUxTRX0KRGltSGVhdG1hcChzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBkaW1zID0gMTozMCwgY2VsbHMgPSA1MDAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYAoKUnVuIFVNQVAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNH0KZGltcy51c2U8LTMwICMjIyBGaW5hbCBQQyBzZWxlY3Rpb24gZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMKc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCA8LSBSdW5VTUFQKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsIGRpbXMgPSAxOmRpbXMudXNlLCB2ZXJib3NlPUYpCmBgYAoKCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDd9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCwgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKSsKICBnZ3RpdGxlKHBhc3RlMCgiU2FtcGxlczogVU1BUCBQQz0gIixkaW1zLnVzZSkpCmBgYAoKIyMjIyBFeHBsb3JlIERFIGdlbmVzIGJldHdlZW4gY2x1c3RlciBUQU01IGFuZCBUQU0xIHRocm91Z2ggVm9sY2FubyBwbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDh9CmNvbnRyYXN0Lm5hbWU9IlRBTTV2c1RBTTEiCkRFR19UQU01dnNUQU0xPUZpbmRNYXJrZXJzKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsaWRlbnQuMSA9ICJUQU0gNSIsaWRlbnQuMiA9ICJUQU0gMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2NvdW50LnVzZSA9IDAuMDEsIHZlcmJvc2UgPSBGKQpERUdfVEFNNXZzVEFNMSRnZW5lPXJvd25hbWVzKERFR19UQU01dnNUQU0xKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gOH0KZ2dwbG90KGRhdGE9REVHX1RBTTV2c1RBTTEsIGFlcyh4PSBhdmdfbG9nRkMsIHkgPSAtbG9nMTAocF92YWxfYWRqKSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLCBzaXplPTIsIAogICAgICAgICAgICAgICBhZXMoY29sID0gcF92YWxfYWRqPDFlLTIwICYgKGF2Z19sb2dGQz4xIHwgYXZnX2xvZ0ZDPCAtMSkpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGFyayBncmV5IiwgImJyb3duMSIpKSArCiAgICBnZW9tX3RleHRfcmVwZWwoZGF0YT1zdWJzZXQoREVHX1RBTTV2c1RBTTEsICAKICAgICAgICAgICAgICAgICAgICBwX3ZhbF9hZGo8MWUtMTAwICYgKGF2Z19sb2dGQz4xIHwgYXZnX2xvZ0ZDPCAtMSkpLGFlcyhsYWJlbCA9IGdlbmUpLCBzaXplID00KSsKICAgIHRoZW1lX2NsYXNzaWMoKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2dndGl0bGUoY29udHJhc3QubmFtZSkKYGBgCgojIyMgR2VuZSBPbnRvbG9neSBlbnJpY2htZW50IHdpdGggTWV0YXNjYXBlIG9mIHRoZSBnZW5lcyB1cHJlZ3VsYXRlZCBpbiBjbHVzdGVyIFRBTTUgY29tcGFyZWQgdG8gY2x1c3RlciBUQU0xClNlbGVjdCB0b3AgdXByZWd1bGF0ZWQgREVHIChhcyB0aG9zZSBnZW5lcyBhcmUgc3BlY2lmaWMgZm9yIHRoZSBjbHVzdGVyIG9mIGludGVyZXN0KS4gSXQgaXMgYmVzdCB0byBoYXZlIGF0IGxlYXN0IDIwLTUwIGdlbmVzIGZvciBHTyBlbnJpY2htZW50LgpgYGB7cn0KcF90cmVzaD0xZS0yMApsb2dGQ190aHJlc2g9MQpzZWxlY3RlZC5nZW5lcz1ERUdfVEFNNXZzVEFNMVtERUdfVEFNNXZzVEFNMSRwX3ZhbF9hZGo8cF90cmVzaCAmIERFR19UQU01dnNUQU0xJGF2Z19sb2dGQz5sb2dGQ190aHJlc2gsImdlbmUiXQpsZW5ndGgoc2VsZWN0ZWQuZ2VuZXMpCmBgYApFeHBvcnQgZ2VuZSBuYW1lcyBhcyBhIGNzdiBmaWxlIGZvciBtZXRhc2NhcGUKYGBge3J9CndyaXRlLnRhYmxlKHNlbGVjdGVkLmdlbmVzLCBmaWxlPXBhc3RlMCgiVXByZWd1bGF0ZWRfZ2VuZXNfIixjb250cmFzdC5uYW1lLCJfcF92YWxfYWRqLiIscF90cmVzaCwiX2xvZ0ZDLiIsbG9nRkNfdGhyZXNoLCIuY3N2IiksIHJvdy5uYW1lcyA9IEYsIGNvbC5uYW1lcyA9IEYsIHNlcD0iLCIpCmBgYAojIyMjIyMgUnVuIEdPIGVucmljaG1lbnQgaW4gTWV0YXNjYXBlIChodHRwczovL21ldGFzY2FwZS5vcmcvKToKIyMjIyMjIElucHV0IHRoZSAuY3N2IGZpbGUgYW5kIGRvIEV4cHJlc3MgYW5hbHlzaXMgd2l0aCAiSW5wdXQgYXMiPSBNLm11c2N1bHVzIGFuZCAiYW5hbHlzaXMgYXMiPSBNLm11c2N1bHVzID0+IEV4cHJlc3MgQW5hbHlzaXMuIFNhdmUgdGhlICJHZW5lIExpc3QgUmVwb3J0IEV4Y2VsIFNoZWV0cyIKIyMjIyMgIFZpc3VhbGl6YXRpb24gb2YgdGhlIGVucmljaGVkIHBhdGh3YXlzIG9uIHRoZSBWb2xjYW5vIHBsb3Q6CgpgYGB7cn0KR08ubWV0YXNjYXBlPXhsc3g6OnJlYWQueGxzeCggcGFzdGUwKCJNZXRhc2NhcGVfVXByZWd1bGF0ZWRfZ2VuZXNfIixjb250cmFzdC5uYW1lLCJfcF92YWxfYWRqLiIscF90cmVzaCwiX2xvZ0ZDLiIsbG9nRkNfdGhyZXNoLCIueGxzeCIpLDIgKQojU2VsZWN0IG9ubHkgdGhlIG1haW4gKHN1bW1hcnkpIHBhdGh3YXlzCkdPLm1ldGFzY2FwZSRncm91cD1zYXBwbHkoc3Ryc3BsaXQoR08ubWV0YXNjYXBlJEdyb3VwSUQsICJfIiksICJbWyIsMikKYGBgCgojIyMgVG9wIEdPIGVucmljaGVkIHBhdGh3YXk6CmBgYHtyICwgZmlnLmhlaWdodCA9NCwgZmlnLndpZHRoID0gMTAgLHdhcm5pbmc9RkFMU0V9CmluZGV4PTEKZ3NldC5zeW1ib2xzPUdPLm1ldGFzY2FwZVtpbmRleCwiU3ltYm9scyJdCmdzZXQuc3ltYm9scz1zdHJzcGxpdChnc2V0LnN5bWJvbHMsICIsIilbWzFdXQpHT3Rlcm09R08ubWV0YXNjYXBlW2luZGV4LCJEZXNjcmlwdGlvbiJdCmdncGxvdChkYXRhPURFR19UQU01dnNUQU0xLCBhZXMoeD0gYXZnX2xvZ0ZDLCB5ID0gLWxvZzEwKHBfdmFsX2FkaikpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMSwgc2l6ZT0yLAogICAgICAgICAgICAgICBhZXMoY29sb3I9Z2VuZSAlaW4lIGdzZXQuc3ltYm9scykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrIGdyZXkiLCAiYnJvd24xIikpICsKICAgIGdlb21fdGV4dF9yZXBlbChkYXRhPXN1YnNldChERUdfVEFNNXZzVEFNMSwgIAogICAgICAgICAgICAgICAgICAgZ2VuZSAlaW4lIGdzZXQuc3ltYm9scyksYWVzKGxhYmVsID0gZ2VuZSksIHNpemUgPTQpKwogICAgdGhlbWVfY2xhc3NpYygpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrZ2d0aXRsZShwYXN0ZTAoIGNvbnRyYXN0Lm5hbWUsICI6ICIsIEdPdGVybSwgIiBwYXRod2F5IikpCmBgYAoKIyMjICJyZXNwb25zZSB0byBoeXBveGlhIiBwYXRod2F5OgpgYGB7ciAsIGZpZy5oZWlnaHQgPTQsIGZpZy53aWR0aCA9IDEwICx3YXJuaW5nPUZBTFNFfQpHT3Rlcm09InJlc3BvbnNlIHRvIGh5cG94aWEiCmdzZXQuc3ltYm9scz1HTy5tZXRhc2NhcGVbR08ubWV0YXNjYXBlJERlc2NyaXB0aW9uPT1HT3Rlcm0sIlN5bWJvbHMiXQpnc2V0LnN5bWJvbHM9c3Ryc3BsaXQoZ3NldC5zeW1ib2xzLCAiLCIpW1sxXV0KZ2dwbG90KGRhdGE9REVHX1RBTTV2c1RBTTEsIGFlcyh4PSBhdmdfbG9nRkMsIHkgPSAtbG9nMTAocF92YWxfYWRqKSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLCBzaXplPTIsCiAgICAgICAgICAgICAgIGFlcyhjb2xvcj1nZW5lICVpbiUgZ3NldC5zeW1ib2xzKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmsgZ3JleSIsICJicm93bjEiKSkgKwogICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9c3Vic2V0KERFR19UQU01dnNUQU0xLCAgCiAgICAgICAgICAgICAgICAgICBnZW5lICVpbiUgZ3NldC5zeW1ib2xzKSxhZXMobGFiZWwgPSBnZW5lKSwgc2l6ZSA9NCkrCiAgICB0aGVtZV9jbGFzc2ljKCkrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStnZ3RpdGxlKHBhc3RlMCggY29udHJhc3QubmFtZSwgIjogIiwgR090ZXJtLCAiIHBhdGh3YXkiKSkKYGBgCgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTB9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCxsYWJlbD1ULCByZXBlbD1UKSsKICBnZ3RpdGxlKHBhc3RlMCgiQ2VsbCBhbm5vdGF0aW9uOiBVTUFQIFBDPSAiLGRpbXMudXNlKSkKYGBgCgojIyMjIyBTYXZlIG9iamVjdApgYGB7cn0Kc2F2ZVJEUyhzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBmaWxlPSJDaXRlc2VxX21vdXNlX0dCTS5zZXVyYXRPYmoucmRzIikKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpzZXNzaW9uSW5mbygpCmBgYAoKCg==